package com.server.core;

import java.io.File;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.ServerSocket;

import org.eclipse.jetty.server.NetworkTrafficServerConnector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SessionManager;
import org.eclipse.jetty.server.session.HashSessionManager;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.webapp.WebAppContext;

import com.server.kit.FileKit;
import com.server.kit.PathKit;
import com.server.kit.StrKit;

class JettyServer implements IServer {

    private String        webAppDir;
    private int           port;
    private String        context;
    private int           scanIntervalSeconds;
    private boolean       running = false;
    private Server        server;
    private WebAppContext webApp;

    JettyServer(String webAppDir, int port, String context, int scanIntervalSeconds) {
        if (webAppDir == null)
            throw new IllegalStateException("Invalid webAppDir of web server: " + webAppDir);
        if ((port < 0) || (port > 65536))
            throw new IllegalArgumentException("Invalid port of web server: " + port);
        if (StrKit.isBlank(context)) {
            throw new IllegalStateException("Invalid context of web server: " + context);
        }
        this.webAppDir = webAppDir;
        this.port = port;
        this.context = context;
        this.scanIntervalSeconds = scanIntervalSeconds;
    }

    public void start() {
        if (!this.running) {
            try {
                doStart();
            } catch (Exception e) {
                e.printStackTrace();
            }
            this.running = true;
        }
    }

    public void stop() {
        if (this.running) {
            try {
                this.server.stop();
            } catch (Exception e) {
                e.printStackTrace();
            }
            this.running = false;
        }
    }

    private void doStart() throws IOException {
        if (!available(this.port)) {
            throw new IllegalStateException("port: " + this.port + " already in use!");
        }
        deleteSessionData();
        System.out.println("Starting jetty server...");
        this.server = new Server();
        ServerConnector connector = new NetworkTrafficServerConnector(this.server);
        connector.setPort(this.port);
        this.server.addConnector(connector);
        this.webApp = new WebAppContext();
        this.webApp.setThrowUnavailableOnStartupException(true);
        this.webApp.setContextPath(this.context);
        this.webApp.setResourceBase(this.webAppDir);
        this.webApp.setInitParameter("org.eclipse.jetty.servlet.Default.dirAllowed", "false");
        this.webApp.setInitParameter("org.eclipse.jetty.servlet.Default.useFileMappedBuffer", "false");
        persistSession(this.webApp);
        this.server.setHandler(this.webApp);
        changeClassLoader(this.webApp);
        if (this.scanIntervalSeconds > 0) {
            Scanner scanner = new Scanner(PathKit.getRootClassPath(), this.scanIntervalSeconds) {

                public void onChange() {
                    try {
                        System.err.println("\nLoading changes ......");
                        JettyServer.this.webApp.stop();
                        ServerClassLoader loader = new ServerClassLoader(JettyServer.this.webApp, JettyServer.this.getClassPath());
                        JettyServer.this.webApp.setClassLoader(loader);
                        JettyServer.this.webApp.start();
                        System.err.println("Loading complete.");
                    } catch (Exception e) {
                        System.err.println("Error reconfiguring/restarting webapp after change in watched files");
                        e.printStackTrace();
                    }
                }
            };
            System.out.println("Starting scanner at interval of " + this.scanIntervalSeconds + " seconds.");
            scanner.start();
        }
        try {
            System.out.println("Starting web server on port: " + this.port);
            this.server.start();
            System.out.println("Starting Completed.");
            this.server.join();
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(100);
        }
    }

    @SuppressWarnings("resource")
    private void changeClassLoader(WebAppContext webApp) {
        try {
            String classPath = getClassPath();
            new ServerClassLoader(webApp, classPath).addClassPath(classPath);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private String getClassPath() {
        return System.getProperty("java.class.path");
    }

    private void deleteSessionData() {
        try {
            FileKit.delete(new File(getStoreDir()));
        } catch (Exception localException) {}
    }

    private String getStoreDir() {
        String storeDir = System.getProperty("java.io.tmpdir") + "/session_data" + this.context;
        if ("\\".equals(File.separator))
            storeDir = storeDir.replaceAll("/", "\\\\");
        return storeDir;
    }

    private void persistSession(WebAppContext webApp) throws IOException {
        String storeDir = getStoreDir();
        SessionManager sm = webApp.getSessionHandler().getSessionManager();
        if ((sm instanceof HashSessionManager)) {
            ((HashSessionManager) sm).setStoreDirectory(new File(storeDir));
            return;
        }
        HashSessionManager hsm = new HashSessionManager();
        hsm.setStoreDirectory(new File(storeDir));
        SessionHandler sh = new SessionHandler();
        sh.setSessionManager(hsm);
        webApp.setSessionHandler(sh);
    }

    private static boolean available(int port) {
        if (port <= 0) {
            throw new IllegalArgumentException("Invalid start port: " + port);
        }
        ServerSocket ss = null;
        DatagramSocket ds = null;
        try {
            ss = new ServerSocket(port);
            ss.setReuseAddress(true);
            ds = new DatagramSocket(port);
            ds.setReuseAddress(true);
            return true;
        } catch (IOException localIOException1) {} finally {
            if (ds != null) {
                ds.close();
            }
            if (ss != null) {
                try {
                    ss.close();
                } catch (IOException localIOException3) {}
            }
        }
        return false;
    }
}
